#pragma once

#include <GL/glew.h>
#include <GL/glut.h>

class IRenderTarget
{
public:
	virtual GLuint getTextureID() = 0;
	virtual void startRender() = 0;
	virtual void endRender() = 0;
	virtual void addtarget(int id = 1) = 0;
};

//render texture with RGBA float16 components
class RenderTexture: public IRenderTarget
{
public:
	RenderTexture(int res_x, int res_y, bool hasDepth = false, int smoothtype = GL_LINEAR, 
		int texturetype = GL_TEXTURE_RECTANGLE_EXT);
	~RenderTexture();
	//get the OpenGL texture handle of the texture
	GLuint getTextureID()
	{
		return renderTexID;
	}

	void startRender()
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, sharedFBO);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, textype, renderTexID, 0);
		if (hasDepth)
			glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depthTexID, 0);
		glViewport(0, 0, res_x, res_y);
	}

	void addtarget(int id = 1)
	{
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + id,textype, renderTexID, 0);		
	}

	void endRender()
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	}

	int getResX() { return res_x;}
	int getResY() { return res_y;}

protected:
	bool hasDepth;
	int res_x;
	int res_y;
	GLuint sharedFBO;
	GLuint renderTexID;
	GLuint depthTexID;
	GLuint textype;
};

// two render textures with RGBA float16 components used to "ping-pong" between them
// these two textures are useful to implement reading from a texture which we are rendering to
class IteratedRenderTexture : public IRenderTarget
{
public:
	~IteratedRenderTexture();
	IteratedRenderTexture(int res_x = 0, int res_y = 0, bool hasDepth = false, 
		int smoothtype = GL_LINEAR, 
		GLenum target = GL_TEXTURE_RECTANGLE_NV,
		GLenum internalformat = GL_RGBA16F_ARB, 
		GLenum format = GL_RGBA,
		GLenum type = GL_HALF_FLOAT_ARB,
		GLvoid* texels = 0);
	// get the OpenGL texture handle of the texture we are reading from
	virtual GLuint getTextureID(){return textureTexID;}	
	// get the OpenGL texture handle of the texture we are writing to
	virtual GLuint getRenderTextureID(){return renderTexID;}
	//start rendering to one of the textures
	virtual void startRender()
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, sharedFBO);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, target, renderTexID, 0);	
		if(hasDepth)
			glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depthTexID, 0);
		glViewport(0, 0, res_x, res_y);			
	}
	// after rendering we swap the two textures
	virtual void endRender()
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		GLuint temp = textureTexID;
		textureTexID = renderTexID;
		renderTexID = temp;
	}

	//end without swapping
	void endRenderWithoutSwap()
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	}

	//render to the texture as another render target
	virtual void addtarget(int id = 1)
	{
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + id, target, renderTexID, 0);			
	}
	// start rendering to the texture we would normally read from
	virtual void startCopy()
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, sharedFBO);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, target, textureTexID, 0);		
	}
	//don't swap after rendering to the texture we usually read from 
	virtual void endCopy()
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);			
	}

	virtual void reset();
	int getResX() { return res_x;}
	int getResY() { return res_y;}

protected:
	bool hasDepth;
	int res_x;
	int res_y;
	GLuint sharedFBO;
	GLuint textureTexID;
	GLuint renderTexID;	
	GLuint depthTexID;
	GLenum target;
};


//created to be used with inter-density-field filter
class MultiIteratedRenderTexture : public IteratedRenderTexture
{
public:
	~MultiIteratedRenderTexture();
	MultiIteratedRenderTexture(int res_x, int res_y, int smoothtype = GL_LINEAR, 
		int texturetype =  GL_TEXTURE_RECTANGLE_EXT);
	// get the OpenGL texture handle of the texture we are reading from
	virtual GLuint getTextureID(){
		return textureId[1];
	}	
	// get the OpenGL texture handle of the texture we are writing to
	virtual GLuint getRenderTextureID(){
		return textureId[0];
	}
	//start rendering to one of the textures
	virtual void startRender()
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, sharedFBO);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_EXT, 
			textureId[0], 0);	
		glViewport(0, 0, res_x, res_y);			
	}
	// after rendering we swap the two textures
	virtual void endRender()
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		GLuint temp = textureId[nTexture -1];
		for (int i = nTexture - 1; i > 0; i--)
		{
			textureId[i] = textureId[i - 1];
		}
		textureId[0] = temp;
	}
	//render to the texture as another render target
	virtual void addtarget(int id = 1)
	{
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + id, GL_TEXTURE_RECTANGLE_EXT, 
			textureId[0], 0);
	}
	// start rendering to the texture we would normally read from
	virtual void startCopy()
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, sharedFBO);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_EXT, 
			textureId[1], 0);		
	}
	//don't swap after rendering to the texture we usually read from 
	virtual void endCopy()
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);			
	}

	virtual void reset();

	GLuint* getTextureIdList(){ return textureId;}
	int getTextureNumber() { return nTexture;}

protected:
	int res_x;
	int res_y;
	int nTexture;
	int Idtex;
	GLuint sharedFBO;
	GLuint* textureId;
};